`include "defines.v"

module clint (
    input  wire                                clock,
    input  wire                                reset_n,

    output wire                                intr,
    input  wire [ 5: 0]                        mtimefreq,

    output wire                                clint_aw_ready,
    input  wire                                clint_aw_valid,
    input  wire [`AXI_ADDR_WIDTH-1:0]          clint_aw_addr,
    input  wire [2:0]                          clint_aw_prot,
    input  wire [`AXI_ID_WIDTH+1:0]            clint_aw_id,
    input  wire [`AXI_USER_WIDTH-1:0]          clint_aw_user,
    input  wire [7:0]                          clint_aw_len,
    input  wire [2:0]                          clint_aw_size,
    input  wire [1:0]                          clint_aw_burst,
    input  wire                                clint_aw_lock,
    input  wire [3:0]                          clint_aw_cache,
    input  wire [3:0]                          clint_aw_qos,
    input  wire [3:0]                          clint_aw_region,

    output wire                                clint_w_ready,
    input  wire                                clint_w_valid,
    input  wire [`AXI_DATA_WIDTH-1:0]          clint_w_data,
    input  wire [`AXI_DATA_WIDTH/8-1:0]        clint_w_strb,
    input  wire                                clint_w_last,
    input  wire [`AXI_USER_WIDTH-1:0]          clint_w_user,
    
    input  wire                                clint_b_ready,
    output wire                                clint_b_valid,
    output wire [1:0]                          clint_b_resp,
    output wire [`AXI_ID_WIDTH+1:0]            clint_b_id,
    output wire [`AXI_USER_WIDTH-1:0]          clint_b_user,

    output wire                                clint_ar_ready,
    input  wire                                clint_ar_valid,
    input  wire [`AXI_ADDR_WIDTH-1:0]          clint_ar_addr,
    input  wire [2:0]                          clint_ar_prot,
    input  wire [`AXI_ID_WIDTH+1:0]            clint_ar_id,
    input  wire [`AXI_USER_WIDTH-1:0]          clint_ar_user,
    input  wire [7:0]                          clint_ar_len,
    input  wire [2:0]                          clint_ar_size,
    input  wire [1:0]                          clint_ar_burst,
    input  wire                                clint_ar_lock,
    input  wire [3:0]                          clint_ar_cache,
    input  wire [3:0]                          clint_ar_qos,
    input  wire [3:0]                          clint_ar_region,
    
    input  wire                                clint_r_ready,
    output wire                                clint_r_valid,
    output wire [1:0]                          clint_r_resp,
    output wire [`AXI_DATA_WIDTH-1:0]          clint_r_data,
    output wire                                clint_r_last,
    output wire [`AXI_ID_WIDTH+1:0]            clint_r_id,
    output wire [`AXI_USER_WIDTH-1:0]          clint_r_user,

    //for sim
    output wire clint_skip        //TODO:delete before soc
);

    assign clint_skip = (clint_r_valid & clint_r_ready & clint_r_last) | (clint_b_valid & clint_b_ready);//TODO:delete before soc

    reg [63: 0] mtime;
    reg [63: 0] mtimecmp;
    reg [ 5: 0] mtimediv;

    always @(posedge clock) begin                                       //TODO:realize mtimediv & customized reserved csr
        if(~reset_n) mtimediv <= 0;
        else if(mtimediv < mtimefreq) mtimediv <= mtimediv + 6'b1;
        else mtimediv <= 0;
    end

    assign intr = mtime >= mtimecmp;

    //axi
    //handshake
    wire aw_hs      = clint_aw_ready & clint_aw_valid;
    wire w_hs       = clint_w_ready  & clint_w_valid;
    wire b_hs       = clint_b_ready  & clint_b_valid;
    wire ar_hs      = clint_ar_ready & clint_ar_valid;
    wire r_hs       = clint_r_ready  & clint_r_valid;

    wire w_done     = w_hs & clint_w_last;
    wire r_done     = r_hs & clint_r_last;
    wire b_done     = b_hs;

    //state machine
    parameter [1:0] W_STATE_IDLE = 2'b00, W_STATE_ADDR = 2'b01, W_STATE_WRITE = 2'b10, W_STATE_RESP = 2'b11;
    parameter [1:0] R_STATE_IDLE = 2'b00, R_STATE_ADDR = 2'b01, R_STATE_READ  = 2'b10;

    reg [1:0] w_state, r_state;
    wire w_state_idle = w_state == W_STATE_IDLE, w_state_addr = w_state == W_STATE_ADDR, w_state_write = w_state == W_STATE_WRITE, w_state_resp = w_state == W_STATE_RESP;
    wire r_state_idle = r_state == R_STATE_IDLE, r_state_addr = r_state == R_STATE_ADDR, r_state_read  = r_state == R_STATE_READ;

    // Wirte State Machine
    always @(posedge clock) begin
        if (~reset_n) begin
            w_state <= W_STATE_IDLE;
        end
        else begin
            case (w_state)
                W_STATE_IDLE:               w_state <= W_STATE_ADDR;
                W_STATE_ADDR:  if (aw_hs)   w_state <= W_STATE_WRITE;
                W_STATE_WRITE: if (w_done)  w_state <= W_STATE_RESP;
                W_STATE_RESP:  if (b_hs)    w_state <= W_STATE_IDLE;
                default : ;
            endcase
        end
    end

    // Read State Machine
    always @(posedge clock) begin
        if (~reset_n) begin
            r_state <= R_STATE_IDLE;
        end
        else begin
            case (r_state)
                R_STATE_IDLE:               r_state <= R_STATE_ADDR;
                R_STATE_ADDR: if (ar_hs)    r_state <= R_STATE_READ;
                R_STATE_READ: if (r_done)   r_state <= R_STATE_IDLE;
                default:;
            endcase
        end
    end

    //------------------Number of transmission------------------
    //-------------------read----------------------
    reg [7:0] r_len;
    wire r_len_reset_n      = reset_n & ~r_state_idle;
    wire r_len_incr_en    = (r_len != axi_r_len) & r_hs;
    always @(posedge clock) begin
        if (~r_len_reset_n) begin
            r_len <= 0;
        end
        else if (r_len_incr_en) begin
            r_len <= r_len + 1;
        end
    end

    //-------------------write----------------------
    reg [7:0] w_len;
    wire w_len_reset_n      = ~reset_n & ~b_done;
    wire w_len_incr_en    = (w_len != axi_w_len) & w_hs;
    always @(posedge clock) begin
        if (~w_len_reset_n) begin
            w_len <= 0;
        end
        else if (w_len_incr_en) begin
            w_len <= w_len + 1;
        end
    end

    // ------------------Process Data------------------
    parameter ALIGNED_WIDTH = $clog2(`AXI_DATA_WIDTH / 8);
    parameter OFFSET_WIDTH  = $clog2(`AXI_DATA_WIDTH);
    parameter MASK_WIDTH    = `AXI_DATA_WIDTH * 2;
    parameter TRANS_LEN     = `RW_DATA_WIDTH / `AXI_DATA_WIDTH;
    parameter BLOCK_TRANS   = TRANS_LEN > 1 ? 1'b1 : 1'b0;
    //-------------------read----------------------
    wire [7:0] axi_r_len      = clint_ar_len;
    reg  [`AXI_ID_WIDTH+1:0]   r_id;

    always @(posedge clock) begin
        if (~reset_n) begin
            r_id   <= 0;
        end
        else if (ar_hs) begin
            r_id   <= clint_ar_id;
        end
        else begin
            r_id   <= r_id;
        end
    end

    //-------------------write----------------------
    wire [7:0] axi_w_len      = clint_aw_len;
    reg  [`AXI_ADDR_WIDTH-1:0] w_addr;
    reg  [`AXI_ID_WIDTH+1:0]   w_id;

    always @(posedge clock) begin
        if (~reset_n) begin
            w_addr <= 0;
            w_id   <= 0;
        end
        else if (aw_hs) begin
            w_addr <= clint_aw_addr;
            w_id   <= clint_aw_id;
        end
        else begin
            w_addr <= w_addr;
            w_id   <= w_id;
        end
    end

    // ------------------Write Transaction------------------
    // Write address channel signals
    assign clint_aw_ready = w_state_addr | w_state_write;

    // Write data channel signals
    assign clint_w_ready = w_state_addr | w_state_write;

    //Write response channel signals
    assign clint_b_valid   = w_state_resp;
    assign clint_b_resp    = 2'b00;
    assign clint_b_id      = w_id;


    always @(posedge clock) begin
        if (~reset_n) begin
            mtime <= 0;
            mtimecmp <= 0;
        end
        else if (clint_w_ready & clint_w_valid) begin
            if ({w_addr[63:3] , 3'b0 } == `mtime_addr) begin
                mtime[ 7: 0] <= clint_w_strb[0] ? clint_w_data[ 7: 0] : mtime[ 7: 0];
                mtime[15: 8] <= clint_w_strb[1] ? clint_w_data[15: 8] : mtime[15: 8];
                mtime[23:16] <= clint_w_strb[2] ? clint_w_data[23:16] : mtime[23:16];
                mtime[31:24] <= clint_w_strb[3] ? clint_w_data[31:24] : mtime[31:24];
                mtime[39:32] <= clint_w_strb[4] ? clint_w_data[39:32] : mtime[39:32];
                mtime[47:40] <= clint_w_strb[5] ? clint_w_data[47:40] : mtime[47:40];
                mtime[55:48] <= clint_w_strb[6] ? clint_w_data[55:48] : mtime[55:48];
                mtime[63:56] <= clint_w_strb[7] ? clint_w_data[63:56] : mtime[63:56];
            end
            else if ({w_addr[63:3] , 3'b0 } == `mtimecmp_addr) begin
                mtimecmp[ 7: 0] <= clint_w_strb[0] ? clint_w_data[ 7: 0] : mtimecmp[ 7: 0];
                mtimecmp[15: 8] <= clint_w_strb[1] ? clint_w_data[15: 8] : mtimecmp[15: 8];
                mtimecmp[23:16] <= clint_w_strb[2] ? clint_w_data[23:16] : mtimecmp[23:16];
                mtimecmp[31:24] <= clint_w_strb[3] ? clint_w_data[31:24] : mtimecmp[31:24];
                mtimecmp[39:32] <= clint_w_strb[4] ? clint_w_data[39:32] : mtimecmp[39:32];
                mtimecmp[47:40] <= clint_w_strb[5] ? clint_w_data[47:40] : mtimecmp[47:40];
                mtimecmp[55:48] <= clint_w_strb[6] ? clint_w_data[55:48] : mtimecmp[55:48];
                mtimecmp[63:56] <= clint_w_strb[7] ? clint_w_data[63:56] : mtimecmp[63:56];
            end
        end
        else    mtime <= (mtimediv == mtimefreq) ? (mtime + 64'b1) : mtime;
    end

    // ------------------Read Transaction------------------
    // Read address channel signals
    assign clint_ar_ready = r_state_addr;

    // Read data channel signals
    assign clint_r_valid  = r_state_read;
    assign clint_r_resp   = 2'b00;
    assign clint_r_last   = (clint_r_valid & axi_r_len == r_len);
    assign clint_r_id     = r_id;


    always @(posedge clock) begin
        if (~reset_n) begin
            clint_r_data[`AXI_DATA_WIDTH-1:0] <= 0;
        end
        else if (clint_ar_ready & clint_ar_valid) begin
            clint_r_data[`AXI_DATA_WIDTH-1:0] <= ({clint_ar_addr[63:3] , 3'b0 } == `mtime_addr)    ? mtime    : 
                                                 ({clint_ar_addr[63:3] , 3'b0 } == `mtimecmp_addr) ? mtimecmp : 0;
        end
    end


endmodule